package com.lenovo.study;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

/**
 * Author: fuxh3@lenovo.com
 * Date: Created in 2018/2/9 0009
 */
public class HttpClientUtil {
    private static final Log logger = LogFactory.getLog(HttpClientUtil.class);
    private final static Charset UTF8 = Charset.forName("UTF-8");
    private static PoolingHttpClientConnectionManager cm;
    static {
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllX509TrustManager()}, new java.security.SecureRandom());
            ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();

            LayeredConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sc, SSLConnectionSocketFactory.getDefaultHostnameVerifier());

            Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
                                            .register("http", plainsf)
                                            .register("https", sslsf)
                                            .build();

            cm = new PoolingHttpClientConnectionManager(r);
            cm.setMaxTotal(2000);
            cm.setDefaultMaxPerRoute(200);
            HttpHost localhost = new HttpHost("localhost", 80);
            cm.setMaxPerRoute(new HttpRoute(localhost), 500);
            ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory("httpClient-con-monitor"));
            scheduler.scheduleAtFixedRate(new IdleConnectionMonitor(cm), 1000, 6180, TimeUnit.MILLISECONDS);

        } catch (Exception e) {
            logger.error("com.lenovo.util.HttpClientUtil>PoolingHttpClientConnectionManager initialization failed.");
        }
    }
    private HttpClientUtil(){
    }
    public enum Method {
        GET,POST;
    }
    public static String get(String url, List<NameValuePair> params) {
        return request(url, Method.GET, params, null, UTF8, true);
    }
    public static String post(String url, List<NameValuePair> params) {
        return request(url, Method.POST, params, null, UTF8, true);
    }
    public static String request(String url, Method method, List<NameValuePair> params) {
        return request(url, method, params, null, UTF8, true);
    }
    public static String request(String url, Method method, List<NameValuePair> params, List<NameValuePair> headers) {
        return request(url, method, params, headers, UTF8, true);
    }
    public static String request(String url, Method method, List<NameValuePair> params, List<NameValuePair> headers, Charset charset, boolean useGzip) {
        CloseableHttpClient client = null;
        String result = null;
        InputStream content = null;
        ByteArrayOutputStream baos = null;
        BufferedInputStream buffIs = null;
        InputStream is = null;
        try {
            client = getClient();
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build();
            HttpRequestBase req = null;
            URIBuilder uriBuilder = new URIBuilder(url);
            if(method == Method.GET){
                if(params != null){
                    for (NameValuePair nvp : params) {
                        uriBuilder.addParameter(nvp.getName(), nvp.getValue());
                    }
                }
                req = new HttpGet(uriBuilder.build());
            } else if(method == Method.POST){
                HttpPost httpPost = new HttpPost(uriBuilder.build());
                if(params != null){
                    httpPost.setEntity(new UrlEncodedFormEntity(params));
                }
                req = httpPost;
            } else {
                throw new IllegalArgumentException("Unsupported request type.");
            }
            req.setConfig(requestConfig);
            req.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
            if(useGzip){
                req.setHeader("Accept-Encoding", "gzip");
            }
            req.setHeader("Pragma", "no-cache");
            req.setHeader("Cache-Control", "max-age=0");
            if(headers != null){
                for (NameValuePair hkv: headers) {
                    req.setHeader(hkv.getName(), hkv.getValue());
                }
            }

            HttpResponse response = client.execute(req);
            HttpEntity respentity = response.getEntity();
            content = respentity.getContent();
            baos = new ByteArrayOutputStream();
            buffIs = new BufferedInputStream(content);
            is = null;
            if(useGzip){
                Header respContentEncoding = respentity.getContentEncoding();
                if(respContentEncoding != null && headerMatchKey(".*gzip.*", respContentEncoding)){
                    buffIs.mark((int) (respentity.getContentLength() % (1024 * 500)));//500K 以内可以reset
                    try {
                        is = new GZIPInputStream(buffIs);
                    } catch (Exception e) {//不使用 gz
                        buffIs.reset();
                        is = buffIs;
                    }
                }
            }
            if(is == null){
                is = buffIs;
            }
            byte[] b = new byte[1024];
            int l = 0;
            while( (l =is.read(b)) > -1 ){
                baos.write(b, 0, l);
            }
            byte[] byteArray = baos.toByteArray();
            if(charset == null){
                Header[] respContentType = response.getHeaders("Content-Type");
                for (Header header : respContentType) {
                    Pattern pattern = Pattern.compile("charset\\s*=\\s*([A-Za-z0-9-]+)");
                    Matcher matcher = pattern.matcher(header.getValue());
                    if(matcher.find()){
                        String chaStr = matcher.group(1);
                        try {
                            charset = Charset.forName(chaStr);
                        } catch (Exception e) {
                            logger.error("响应编码异常", e);
                        }
                        break;
                    }
                }
            }
            if(charset == null) charset = UTF8;
            result  = new String(byteArray, charset);

        } catch (Exception e) {
            logger.error("发送请求异常", e);
        } finally {
            closeResource(is, buffIs, baos, content);
        }
        return result;
    }
    private final static class DaemonThreadFactory implements ThreadFactory {
        private String name;
        private AtomicInteger integer = new AtomicInteger(1);
        public DaemonThreadFactory(String name) {
            this.name = name;
        }
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r, name + "-" + integer.getAndIncrement());
            return thread;
        }

    }

    private final static class IdleConnectionMonitor implements Runnable {
        private PoolingHttpClientConnectionManager cm;
        public IdleConnectionMonitor(PoolingHttpClientConnectionManager cm) {
            this.cm = cm;
        }
        @Override
        public void run() {
            if( cm.getTotalStats().getAvailable() != 0 ){
                cm.closeExpiredConnections();
                cm.closeIdleConnections(10000, TimeUnit.MILLISECONDS);
                logger.debug("Idle connection count : " + cm.getTotalStats().getAvailable() + ".");
            }
        }
    }
    private static void closeResource(Closeable... resources){
        for (Closeable resource : resources) {
            if(resource != null){
                try {
                    resource.close();
                } catch (Exception e) {
                }
            }
        }
    }

    private static boolean headerMatchKey(String keyPattern, Header... headers){
        for (Header header : headers) {
            if(header.getValue().matches(keyPattern)){
                return true;
            }
        }
        return false;
    }

    private static CloseableHttpClient getClient(){
        return HttpClients.custom().setConnectionManager(cm).build();
    }

    public static final class TrustAllX509TrustManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[] {};
        }
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws
                                        CertificateException {
        }
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
    }
}
